﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

namespace Xenta.Utils
{
    /// <summary>
    /// Represents the reflection helper.
    /// </summary>
    public static class ReflectionUtility
    {
        /// <summary>
        /// Determines if reflected member contains the required attribute.
        /// </summary>
        /// <typeparam name="TAttribute">The type of attribute.</typeparam>
        /// <param name="member">The member info.</param>
        /// <returns>true if the attribute is presented; otherwise false.</returns>
        public static bool HasAttribute<TAttribute>(MemberInfo member) 
            where TAttribute : Attribute
        {
            return Attribute<TAttribute>(member) != null;
        }

        /// <summary>
        /// Gets the attribute from reflected member.
        /// </summary>
        /// <typeparam name="TAttribute">The type of attribute.</typeparam>
        /// <param name="member">The member info.</param>
        /// <returns>The attribute object or null.</returns>
        public static TAttribute Attribute<TAttribute>(MemberInfo member) 
            where TAttribute : Attribute
        {
            return System.Attribute.GetCustomAttribute(member, typeof(TAttribute)) as TAttribute;
        }

        /// <summary>
        /// Determines whethere the type implements an interface.
        /// </summary>
        /// <typeparam name="TInterface">The interface type.</typeparam>
        /// <param name="type">The type.</param>
        /// <returns>true if interface is implemented by type; otherwise false.</returns>
        public static bool DoesImplementInterface<TInterface>(Type type)
        {
            return type.GetInterfaces().Any(x => x == typeof(TInterface));
        }

        /// <summary>
        /// Gets the enumeration of assemblies located under file system path.
        /// </summary>
        /// <param name="path">The file system path.</param>
        /// <returns>The enumeration of assemblies.</returns>
        public static IEnumerable<Assembly> LoadAssemblies(string path)
        {
            if(!Directory.Exists(path))
                return Enumerable.Empty<Assembly>();
            var assemblies = new List<Assembly>();
            foreach(string i in Directory.EnumerateFiles(path, "*.dll"))
            {
                try
                {
                    assemblies.Add(Assembly.Load(new AssemblyName
                    {
                        CodeBase = i
                    }));
                }
                catch(FileNotFoundException)
                {
                }
                catch(FileLoadException)
                {
                }
                catch(BadImageFormatException)
                {
                }
                catch(ReflectionTypeLoadException)
                {
                }
            }
            return assemblies;
        }

        /// <summary>
        /// Lists types of assemblies filtered using fiter.
        /// </summary>
        /// <param name="assemblies">The assemblies.</param>
        /// <param name="filter">The filter.</param>
        /// <returns>The enumeration of types.</returns>
        public static IEnumerable<Type> AssemblyTypes(IEnumerable<Assembly> assemblies, Predicate<Type> filter)
        {
            foreach(var assembly in assemblies)
                foreach(var t in AssemblyTypes(assembly, filter))
                    yield return t;
        }

        /// <summary>
        /// Lists types of assembly filtered using fiter.
        /// </summary>
        /// <param name="filter">The filter.</param>
        /// <param name="assembly">The assembly.</param>
        /// <returns>The enumeration of types.</returns>
        public static IEnumerable<Type> AssemblyTypes(Assembly assembly, Predicate<Type> filter)
        {
            foreach(var t in assembly.GetTypes()
                .Where(i => filter(i)))
            {
                yield return t;
            }
        }

        /// <summary>
        /// Lists interfaces of type filtered using fiter.
        /// </summary>
        /// <param name="filter">The filter.</param>
        /// <param name="type">The type.</param>
        /// <returns>The enumeration of interface types.</returns>
        public static IEnumerable<Type> Interfaces(Type type, Predicate<Type> filter)
        {
            return type.GetInterfaces().Where(i => filter(i));
        }
    }
}
